(ns rete.core)

;; The Environment is a collection of the working memory and contains the information which object fulfills which predicate (node in the Graph) 
;; in a bidirectional way. So it is possible to make a fast selection for predicates and objects, without to traverse the entire graph. 
(defrecord Environment [working-memory graph])

(defprotocol EnvironmentProtocol
  "Definition of a protocol for environments"
  (get-objects [^Environment this object-names] "Returns all objects with the specified name in the working-memory."))

;;Object information for the working memory (WME = Working Memory Element). Contains the object and a set of nodes for which this object is fulfilled.
(defrecord WME [object nodes])

;; The Graph contains the information of the nodes, their parents and their children and the information which node is a root node. To get actions faster, the names of the action nodes are stored separatly.
(defrecord Graph [rootnodes nodes actions mutablenodes])

(defprotocol GraphProtocol
  "Definition of a protocol for graphs"
  (get-node [^Graph this node-name] "(get-node g node-name) returns the node or leaf with the appropriate  name. 
If the passed graph 'g' does not contains a node or leaf with such a name then an IllegalStateException is thrown.")
  (get-nodes [^Graph this node-names] "(get-nodes g node-names) returns all given nodes in the specified graph.")
  (rootnode? [^Graph this node-name]"(rootnode? g node-name) returns 'true', if the specifeid node is a root node in the given graph.")
  (get-children-for-node [^Graph this node-name] "(get-children-for-node g node-name) returns the names/IDs of the children of the specified node in the given graph.")
  (get-children-for-nodes [^Graph this node-names]"(get-children-for-nodes g node-names) returns the names/IDs of the children of the specified nodes in the given graph.")
  (get-parents-for-node [^Graph this node-names] "(get-parents-for-node g node-name) returns the names/IDs of the parents of the specified node in the given graph.")
  (get-parents-for-nodes [^Graph this node-names]"(get-parents-for-nodes g node-names ) returns the names/IDs of the parents of the specified nodes in the given graph.")
  (add-child-to-parent [^Graph this child-name parent-name] "(add-child-to-parent g child-name parent-name) adds the specified child to the specified parent. 
If the child is a root node, then it will be removed from the list of root nodes.")
  (add-parent-to-child [^Graph this parent-name child-name] "Convienece function which calls (add-child-to-parent g child-name parent-name)")
  (remove-child-from-parent [^Graph this child-name parent-name] "(remove-child-from-parent g child-name parent-name) removes the specified child from the specified parent.
Both nodes will remains in the graph.") 
  (add-parents-to-child  [^Graph this parent-names child-name] "(add-parents-to-child g parent-names child-name) adds the specified parents to the specified child. 
If the child is a root node, then it will be removed from the list of root nodes.")                 
  (add-children-to-parent[^Graph this children-names parent-name] "(add-children-to-parent g children-names parent-name) adds the specified children to the specified parent. 
If the child is a root node, then it will be removed from the list of root nodes.") 
  (remove-children-from-parent [^Graph this children-names parent-name] "(remove-children-from-parent g children-names parent-name) removes the specified children from the 
specified parent.")
  (remove-parents-from-child [^Graph this parent-names child-name] "(remove-parents-from-child g parent-names child-name) removes the specified parents from the specified child." )
  (add-root-node [^Graph this node] "(add-root-node g node) adds a new node as root node into the defined graph 'g'.")
  (add-node [^Graph this node] "(add-node g node) adds a new node to the graph as a root node. Any parents and children the node allready possessed remains. 
The same counts for any children the defined parent possess. "
            [^Graph this parent-name node] "(add-node g parent-name node) adds a new node to the graph with the defined parent. Any parents and children the node allready possessed remains. The same counts for any children the defined parent possess. ")
  (remove-node [^Graph this node-name] "(remove node g node-name) removes the node from the specified graph. The node is also removed from its parents and children."))

;;Defintion of a rule. All predicates are put into a map for easier selection during the creation of the graph. The implication is the action which should be prformed if all predicates are true.
;;All predicates were always 'AND' associated. To implement an 'OR' association a new rule with changed predicates needs to be created.
(defrecord Rule [name predicates implication])

;; Definition of the needed data structures. The name of any node unique. Because actions may also have children they are implemented as normal nodes.
(defrecord PredicateNode [name type parents children complied-objects predicate])
(defrecord CountingPredicateNode [name type parents children complied-objects predicate])
(defrecord MutablePredicateNode [name type parents children complied-objects predicate depending-objects change-predicate])
(defrecord ImplicationNode [name type parents children complied-objects predicate implication])

(defprotocol Node 
  "Definition of a node protocol"
  (get-name [^Node this] "Returns the name of the node.")
  (get-predicate [^Node this] "Returns the predicate function of this node.")
  (truth-value? [^Node this object env] "Returns the truth value for the passed object in the given environment. 
The truth-value us a number between 0 and 1 which defines the degree of truth this object has. 
0 is the equivalent of false, 1 the equivalent of true.")
  (add-parent [^Node this parent-name] "Adds a new parent for this node in the specified graph.")
  (add-child [^Node this child-name] "Adds a new child for this node in the specified graph.")
  (add-object [^Node this object env] "Adds the object to this node in the given environment.")
  (remove-parent [^Node this parent-name] "Removes a new parent for this node in the specified graph.")
  (remove-child [^Node this child-name] "Removes a new child for this node in the specified graph.")
  (remove-object [^Node this object env] "Removes the object from this node in the given environment.")
  (reevaluate-objects [^Node this objects env] "Reevaluates the passed objects for this node."))

(defprotocol PredicateChange "Definition of a Protocol which changes the predicate of a Predicatenode."
  (change-predicate [^Node node ^Environment env] "Returns a modified version of the predicate for the defined node."))

(defprotocol EnvironmentChange
  "Definition of an entity which can change an evironment"
  (change-environment [^Node this ^Environment env] "Returns a changed environment."))

;; Definition of constants
(def α-node "α")
(def β-node "β")
(def implication-node "implication")
(def priority :priority)
(def threshold 1/2)

;; Defintion of sorting functions
(def ascending-priority (fn  [action_1 action_2]
                          (< (get action_1 priority java.lang.Long/MIN_VALUE) (get action_2 priority java.lang.Long/MIN_VALUE))))

(def descending-priority (fn [action_1 action_2]
                           (> (get action_1 priority java.lang.Long/MIN_VALUE) (get action_2 priority java.lang.Long/MIN_VALUE))))

;; Definition of helper functions
(defn transform-into-truth-value 
  "(transform-into-truth-value result) transforms the passed value into a truth-value. 
If the passed result is not a number, then 1 is returned if (true? result) returns true, else 0.
0 will also be returned if result is a number which is not in the intervall [0,1]."
  [result]
  (cond
    (number? result) (if (and (<= result 1) 
                              (>= result 0)) result 0)
    (true? result) 1
    :else 0))